package com.bite.messageQueue.mqServer.core;

/**
 * 实现交换机的转发规则
 * 借助这个类验证bindingKey是否合法
 */
public class Router {
    /**
     * BindingKey
     * 1、由数字、字符、下划线组成
     * 2、使用.分割为多个部分
     * 3、使用*表示匹配一个部分
     * 4、使用#表示匹配多个部分
     */
    public boolean checkBindingKey(String bindingKey) {
        //空字符串，直接交换机/扇片交换机
        if (bindingKey.length() == 0) {
            return true;
        }
        //检查每一位字符是否合法
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            if (ch == '.' || ch == '_' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
        //检查*或者#是否为独立的部分
        //如：aaa.*.bbb合法,aaa.*bb.bbb不合法,#同理
        String[] parts = bindingKey.split("\\.");
        for (String part : parts) {
            if (part.length() > 1 && (part.contains("*") || part.contains("#"))) {
                return false;
            }
        }
        //相邻情况
        // 为啥这么约定? 因为前三种相邻的时候, 实现匹配的逻辑会非常繁琐, 同时功能性提升不大~~
        // 1. aaa.#.#.bbb    => 非法
        // 2. aaa.#.*.bbb    => 非法
        // 3. aaa.*.#.bbb    => 非法
        // 4. aaa.*.*.bbb    => 合法
        for (int i = 0; i < parts.length - 1; i++) {
            // 连续两个 ##
            if (parts[i].equals("#") && parts[i + 1].equals("#")) {
                return false;
            }
            // # 连着 *
            if (parts[i].equals("#") && parts[i + 1].equals("*")) {
                return false;
            }
            // * 连着 #
            if (parts[i].equals("*") && parts[i + 1].equals("#")) {
                return false;
            }
        }
        return true;
    }

    /**
     * RoutingKey
     * 1、由数字、字符、下划线组成
     * 2、使用.分割为多个部分
     */
    public boolean checkRoutingKey(String routingKey) {
        //空字符串
        if (routingKey.length() == 0) {
            return true;
        }
        //检查每一位字符是否合法
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            if (ch == '.' || ch == '_') {
                continue;
            }
            return false;
        }
        return true;
    }

    //判断交换机类型并选择对应的转发规则
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) {
        //根据不同的exchangeType类型使用不同的转发规则
        if (exchangeType == ExchangeType.FANOUT) {
            return true;
        } else if (exchangeType == ExchangeType.TOPIC) {
            return routeTopic(binding, message);
        } else {
            throw new RuntimeException("不支持的exchangeType类型");
        }
    }

    // [测试用例]
    // binding key          routing key         result
    // aaa                  aaa                 true
    // aaa.bbb              aaa.bbb             true
    // aaa.bbb              aaa.bbb.ccc         false
    // aaa.bbb              aaa.ccc             false
    // aaa.bbb.ccc          aaa.bbb.ccc         true
    // aaa.*                aaa.bbb             true
    // aaa.*.bbb            aaa.bbb.ccc         false
    // *.aaa.bbb            aaa.bbb             false
    // #                    aaa.bbb.ccc         true
    // aaa.#                aaa.bbb             true
    // aaa.#                aaa.bbb.ccc         true
    // aaa.#.ccc            aaa.ccc             true
    // aaa.#.ccc            aaa.bbb.ccc         true
    // aaa.#.ccc            aaa.aaa.bbb.ccc     true
    // #.ccc                ccc                 true
    // #.ccc                aaa.bbb.ccc         true
    //判断主题交换机暗号是否对应
    private boolean routeTopic(Binding binding, Message message) {
        String[] bindingKeyParts = binding.getBindingKey().split("\\.");
        String[] routingKeyParts = message.getRoutingKey().split("\\.");
        int bindingIndex = 0;
        int routingIndex = 0;

        while (bindingIndex < bindingKeyParts.length && routingIndex < routingKeyParts.length) {
            //情况一: bindingKeyParts[bindingIndex] == routingKeyParts[routingIndex]
            if (bindingKeyParts[bindingIndex].equals(routingKeyParts[routingIndex])) {
                bindingIndex++;
                routingIndex++;
            }
            //情况二：bindingKeyParts[bindingIndex] == "*",直接匹配成功
            else if(bindingKeyParts[bindingIndex].equals("*")){
                bindingIndex++;
                routingIndex++;
            }
            //情况三：bindingKeyParts[bindingIndex] == "#"，再分情况讨论
            else if(bindingKeyParts[bindingIndex].equals("#")){
                // 如果遇到 #, 需要先看看有没有下一个位置.
                bindingIndex++;
                if (bindingIndex == bindingKeyParts.length) {
                    //表示 # 后面没东西了, 说明此时一定能匹配成功
                    return true;
                }
                // 表示 # 后面还有东西, 拿着这个内容, 去 routingKey 中往后找, 找到对应的位置.
                // findNextMatch 这个方法用来查找该部分在 routingKey 的位置. 返回该下标. 没找到, 就返回 -1
                routingIndex = findNextMatch(routingKeyParts, routingIndex, bindingKeyParts[bindingIndex]);
                if (routingIndex == -1) {
                    // 没找到匹配的结果. 匹配失败
                    return false;
                }
                // 找到的匹配的情况, 继续往后匹配.
                bindingIndex++;
                routingIndex++;
            }else{
                return false;
            }
        }
        // [情况五] 判断两边是否都匹配完
        if (bindingIndex == bindingKeyParts.length && routingIndex == routingKeyParts.length) {
            return true;
        }
        return false;
    }

    private int findNextMatch(String[] routingKeyParts, int routingIndex, String bindingKeyPart) {
        for (int i = routingIndex; i < routingKeyParts.length; i++) {
            if (routingKeyParts[i].equals(bindingKeyPart)) {
                return i;
            }
        }
        return -1;
    }
}
